<?php

/**
 * 框架系统类库
 */
class Sys {

    static private $_cfg;

    /**
     * 防止类实例化或被复制
     *
     */
    private function __construct() {
        
    }

    private function __clone() {
        
    }

    /**
     * 得到 APP 的配置，除第一次读取外，其他都是从缓存变量读取
     * @param string $key 配置项
     * @param boolean $reinject_module_config 是否重新读取模块级别配置文件，否则从模块级别配置也从缓存变量读取
     * @return array|string
     */
    static public function getCfg($key = '', $reinject_module_config = false) {

        // 系统级别配置在第一次加载后，永远都不会重新加载
        if (!self::$_cfg) {
            self::$_cfg = include(DATA_PATH . '/Config.php');
        }

        if ($reinject_module_config && defined('MODULE_PATH') && file_exists(MODULE_PATH . '/Config.php')) {
            // 模块级别配置可通过$reload参数指定是否强制重新加载
            self::$_cfg = array_merge(self::$_cfg, include(MODULE_PATH . '/Config.php'));
        }

        return $key ? (isset(self::$_cfg[$key]) ? self::$_cfg[$key] : null) : self::$_cfg;
    }

    /**
     * 写日志,日志都写在 Application/Logs 目录下
     *
     * @param string $message 日志内容
     * @param string $type 日志类型 'user':用户自定义日志 'app':框架的异常错误 'php':PHP的运行错误
     */
    static public function log($message, $type = 'user') {
        $type = ucfirst(strtolower($type));
        // 得到日志目录
        $path = ROOT_PATH . "/Data/Logs/{$type}/" . date('Y-m');
        if (!file_exists($path)) {
            mkdir($path, 0777, true);
        }
        // 得到日志文件
        $file = $path . '/' . date('Y_m_d') . '.log';
        $msg = "---------------[" . date('Y-m-d H:i:s') . "]---------------" . PHP_EOL . $message . PHP_EOL;
        file_put_contents($file, $msg, FILE_APPEND);
    }

    /**
     * 快速载入系统工具类库,即框架的子目录中的类库
     *
     * @example Sys::load('config') 等价于 new Config()
     * @param object $sysClass 类简名
     * @param mixed $params 构造参数
     */
    static public function load($sysClass, $params = null) {
        $includeFile = CORE_PATH . '/' . $sysClass . '.php';
        if (!file_exists($includeFile)) {
            throw new Exception('类库不存在:' . $includeFile);
        }
        require_once $includeFile;
        if (!class_exists($sysClass, false)) {
            throw new Exception('类未定义:' . $class . ' 在文件:' . $includeFile);
        }
        return new $sysClass($params);
    }

    /**
     * 生成URL地址
     * 
     * @param string $controller 控制器
     * @param string $action  动作名
     * @param array|string $params 参数,可以是字符串或数组，具体如下：
     * @param boolean $transform 是否把参数转换为/分割模式
     * @param string $module 如果没有设置则使用当前模块分组
     *
     * 例子:
     * url('index','test', 'id=1&name=yuanwei'); // 字符串参数
     * url('index','test', array('id'=>1,'name'=>'yuanwei')); // 数组参数
     * 
     * 结果：
     * /home/index/test/id/1/name/yuanwei
     * @return string
     */
    static public function url($controller, $action = Controller::DEFAULT_ACTION, $params = null, $transform = true, $module = null) {

        $request = Request::getInstance();

        // 得到当前模块
        $module === null && defined('MODULE_NAME') && $module = strtolower(MODULE_NAME);

        $controller = str_replace('\\', '/', $controller);

        // 连接控制器和动作
        $url = dirname($_SERVER['SCRIPT_NAME']);
        if (strlen($url) == 1) {
            $url = '/' . $module . '/' . $controller;
        } else {
            $url .= '/' . $module . '/' . $controller;
        }

        $url .= '/' . $action;

        $url = strtolower($url);

        // 连接参数
        if ($transform) {
            if (is_array($params)) {
                foreach ($params as $key => $val) {
                    if (is_array($val)) {
                        $url .= '/' . str_replace(array('&', '='), '/', http_build_query(array($key => $val)));
                    } else {
                        $url .= '/' . $key . '/' . urlencode($val);
                    }
                }
            } elseif ($params) {
                $url .= '/' . str_replace(array('&', '='), '/', $params);
            }
        } else {
            if (is_array($params)) {
                $url .= '?' . http_build_query($params);
            } else {
                $url .= '?' . $params;
            }
        }

        return $url;
    }

    /**
     * 任意多个变量的调试输出
     *
     * @param mixed [$var1,$var2,$var3,...]
     */
    static public function dump() {
        echo '<pre style="font-size:12px; color:#0000FF">' . PHP_EOL;
        $vars = func_get_args();
        foreach ($vars as $var) {
            if (is_array($var)) {
                print_r($var);
            } else if (is_object($var)) {
                echo get_class($var) . " Object";
            } else if (is_resource($var)) {
                echo (string) $var;
            } else {
                echo var_dump($var);
            }
        }
        echo '</pre>' . PHP_EOL;
    }

    /**
     * 与 dump() 方法一样,但会终于程序
     *
     * @param mixed [$var1,$var2,$var3,...]
     */
    static public function dumpExit() {
        foreach (func_get_args() as $var) {
            self::dump($var);
        }
        exit;
    }

    /**
     * 框架调用：自动装入类库
     *
     * @param string $class
     */
    static public function _autoload($class) {
        // 查询框架中定义的类库或在搜索路径下查询
        $includeFile = self::_findClass($class);

        // 在找不到的情况下并且有定义第三方的装载器的话则把继续执行第三方的装载器
        if ($includeFile == '' && count(spl_autoload_functions()) > 1) {
            return true;
        }

        $display_error = self::getCfg('display_error');
        if ($display_error && !file_exists($includeFile)) {
            // (发现这里面抛异常不会被处理,所以只能模似了)
            $fileError = true;
            include CORE_PATH . '/Error.php';
            die;
        }

        // 载入文件 (如果是调试模式下则载入了两次)
        require $includeFile;

        // 调试模式下再判断一下类是否定义正确了
        if ($display_error && !(class_exists($class) || interface_exists($class))) {
            $classError = true;
            include CORE_PATH . '/Error.php';
            die;
        }
    }

    /**
     * 框架调用：查找框架所能处理的类对应文件
     * 
     * 如果类库有命名空间，只要类库文件路径（相对于Libs目录或者根目录）和命名空间是对应的，就可以被加载
     * @param string $class
     * @return string
     */
    static private function _findClass($class) {
        $classfile = ltrim(str_replace('\\', '/', $class), '/') . '.php';

        // 在框架目录查找
        if (file_exists(CORE_PATH . '/' . $classfile)) {
            return CORE_PATH . '/' . $classfile;
        }

        // 在类库目录查找
        if (file_exists(LIBS_PATH . '/' . $classfile)) {
            return LIBS_PATH . '/' . $classfile;
        }

        // 在模块目录查找
        if (file_exists(APP_PATH . '/' . $classfile)) {
            return APP_PATH . '/' . $classfile;
        }

        // 在根目录查找
        if (file_exists(ROOT_PATH . '/' . $classfile)) {
            return ROOT_PATH . '/' . $classfile;
        }

        // 在搜索路径中查询
        $includeFile = '';
        $includePaths = explode(PATH_SEPARATOR, get_include_path());
        foreach ($includePaths as $path) {
            $file = $path . '/' . $classfile;
            if (file_exists($file)) {
                $includeFile = $file;
                break;
            }
        }

        return $includeFile;
    }

    /**
     * 框架调用：异常统一处理,并且根据配置决定处理方式
     *
     * @param \Exception $exception
     */
    static public function _exception($exception) {
        // 如果设置为显示异常 或 为永远抛出的异常则调用 Error 控制器
        if (self::getCfg('display_exception')) {
            // 发送500错误
            if (PHP_SAPI != 'cli') {
                @header('HTTP/1.1 500 Internal Server Error');
            }

            ob_clean();

            $error = '
            <!DOCTYPE html>
            <html>
                <head>
                    <title>Error report</title>
                    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
                    <meta name="viewport" content="width=device-width, initial-scale=1.0">
                </head>
                <body>
                    <div>
                        <p>
                            <b>File:</b> [' . $exception->getLine() . ']' . $exception->getFile() . '
                        </p>
                        <p>
                            <b>Message:</b> [' . $exception->getCode() . ']' . $exception->getMessage() . '
                        </p>
                        <h3>Stack Trace:</h3>
                        <pre>' . $exception->getTraceAsString() . '</pre>

                        <h3>Request Parameters:</h3>
                        <pre>' . var_export(Request::getInstance()->getParam(), true) . '</pre>                           
                    </div>
                </body>
            </html>';
            die($error);
        } else {
            // 写日志
            $msg = 'Exception Message:[' . $exception->getCode() . ']' . $exception->getMessage() . PHP_EOL;
            $msg .= 'Stack Trace:' . $exception->getTraceAsString() . PHP_EOL;
            self::log($msg, 'app');
        }
    }

    /**
     * 根据设置选项进行PHP错误处理
     */
    static public function _error($errno, $errstr, $errfile, $errline, $errcontext) {
        // APP的配置
        $appCfg = self::getCfg();
        // 允许的错误类型将不进行日志记录
        $allowError = $appCfg['allow_error'];
        if (in_array($errno, $allowError)) {
            return;
        }
        // 是显示出来还是写日志
        $display = $appCfg['display_error'];
        if ($display) {

            ob_clean();

            // 发送500错误
            if (PHP_SAPI != 'cli') {
                @header('HTTP/1.1 500 Internal Server Error');
            }

            $msg = '<b>Error:</b>' . $errstr . '<br/>' . PHP_EOL;
            $msg .= '<b>File:[' . $errline . ']</b>' . $errfile . '<br/>' . PHP_EOL;
            if ($errcontext) {
                $msg .= '<b>Parsms:</b>' . print_r($errcontext, true) . '<br/>' . PHP_EOL;
            }
            $msg .= '<br/>';
            echo($msg);
        } else {
            $msg = 'Error:' . $errstr . PHP_EOL;
            $msg .= 'File:[' . $errline . ']' . $errfile . PHP_EOL;
            self::log($msg, 'php');
        }
    }

    /**
     * 检测当前是否以 PHP CLI 方式运行，并把CLI参数写入到Request的参数中
     */
    static public function _checkSapi() {

        if (!(PHP_SAPI == 'cli' || !isset($_SERVER["REQUEST_METHOD"]))) {
            return;
        }

        // 根据框架的配置将 $_SERVER['argv'] 变量的值转成 REQUEST 的 GET 或 PARAM 值
        $request = Request::getInstance();

        $argv = $request->server('argv');

        // 第一个参数为主程序文件名
        unset($argv[0]);

        // 把CLI参数写入到Request的参数中
        foreach ($argv as $arg) {

            list($key, $val) = explode('=', $arg);

            $request->setParam($key, $val);
        }
    }

}
